// CoreCalc, a spreadsheet core implementation
// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Reflection;
using System.Text;
using System.Diagnostics;
using C5;
using CoreCalc.SheetDefinedFunctions;

// Class Value and its subclasses are used to represent the possible 
// values of a spreadsheet cell    

namespace CoreCalc {

  // A Value is the result of evaluating an expression

  public abstract class Value
  {
    public static readonly Type type = typeof(Value);
    public static readonly MethodInfo toObjectMethod
      = type.GetMethod("ToObject", new Type[] { type });
    public static readonly MethodInfo toDoubleOrNanMethod
      = type.GetMethod("ToDoubleOrNan", new Type[] { type });

    public virtual void Apply(Act<Value> act)
    {
      act(this);
    }

    public static Object ToObject(Value v)
    {
      return v.ToObject();
    }

    public static double ToDoubleOrNan(Value v)
    {
      if (v is NumberValue)
        return (v as NumberValue).value;
      else if (v is ErrorValue)
        return (v as ErrorValue).ErrorNan;
      else
        return ErrorValue.argTypeErrorValueNan;
    }

    // For external methods that do not return anything -- or make it null?
    public static Value MakeVoid(Object o /* ignored */)
    {
      return TextValue.VOID;
    }

    public abstract Object ToObject();
  }

  // ----------------------------------------------------------------
  // A NumberValue is a floating-point number

  public class NumberValue : Value {
    public readonly double value;

    public static readonly NumberValue
      ZERO = new NumberValue(0.0),
      ONE = new NumberValue(1.0),
      PI = new NumberValue(Math.PI);

    public new static readonly Type type = typeof(NumberValue);
    public static readonly FieldInfo
      valueField = type.GetField("value"),
      zeroField = type.GetField("ZERO"),
      oneField = type.GetField("ONE"),
      piField = type.GetField("PI");
    public static readonly MethodInfo 
      makeMethod = type.GetMethod("Make", new Type[] { typeof(double) }),
      toDoubleMethod = type.GetMethod("ToDouble"),
      toInt32Method = type.GetMethod("ToInt32"),
      toBooleanMethod = type.GetMethod("ToBoolean"),
      fromDoubleMethod = type.GetMethod("FromDouble"),
      fromInt32Method = type.GetMethod("FromInt32"),
      fromBooleanMethod = type.GetMethod("FromBoolean");


    public NumberValue(double value) {
      Debug.Assert(!Double.IsInfinity(value) && !Double.IsNaN(value));
      this.value = value;
    }

    public static Value Make(double d)
    {
      if (double.IsInfinity(d))
        return ErrorValue.numErrorValue;
      else if (double.IsNaN(d))
        return ErrorValue.FromNan(d);
      else if (d == 0)
        return ZERO;
      else if (d == 1)
        return ONE;
      else if (d == Math.PI)
        return PI;
      else
        return new NumberValue(d);
    }

    public override Object ToObject()
    {
      return (Object)value;
    }

    public static Object ToDouble(Value v)
    {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)nv.value : null;	// Causes boxing
    }

    public static Value FromDouble(Object o)
    {
      if (o is double)
        return Make((double)o);
      else
        return ErrorValue.numErrorValue;
    }

    public static Object ToInt32(Value v)
    {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(int)nv.value : null; // Causes boxing
    }

    public static Value FromInt32(Object o)
    {
      if (o is int)
        return Make((int)o);
      else
        return ErrorValue.numErrorValue;
    }

    public static Object ToBoolean(Value v)
    {
      NumberValue nv = v as NumberValue;
      return nv != null ? (Object)(nv.value != 0) : null;	// Causes boxing
    }

    public static Value FromBoolean(Object o)
    {
      if (o is bool)
        return Make((bool)o ? 1.0 : 0.0);
      else
        return ErrorValue.numErrorValue;
    }

    public override String ToString() {
      return value.ToString();
    }
  }

  // ----------------------------------------------------------------
  // A TextValue is a string

  public class TextValue : Value {
    public readonly String value;

    public static readonly TextValue
      EMPTY = new TextValue(String.Empty),
      VOID = new TextValue("<void>");

    public new static readonly Type type = typeof(TextValue);
    public static readonly FieldInfo
      valueField = type.GetField("value"),
      emptyField = type.GetField("EMPTY"),
      voidField = type.GetField("VOID");
    public static readonly ConstructorInfo
      constructor = type.GetConstructor(new Type[] { typeof(string) });
    public static readonly MethodInfo 
      fromStringMethod = type.GetMethod("FromString"),
      makeMethod = type.GetMethod("Make", new Type[] { typeof(string) }),
      toStringMethod = type.GetMethod("ToString", new Type[] { Value.type });

    public TextValue(String value) {
      this.value = value;
    }

    public static Value Make(string s)
    {
      if (s == null)
        return ErrorValue.argTypeErrorValue;
      else if (s == "")
        return TextValue.EMPTY;
      else
        return new TextValue(s);
    }

    public override Object ToObject()
    {
      return (Object)value;
    }

    public static String ToString(Value v)
    {
      TextValue tv = v as TextValue;
      return tv != null ? tv.value : null;
    }

    public static Value FromString(Object o)
    {
      if (o is String)
        return Make((String)o);
      else
        return ErrorValue.argTypeErrorValue;
    }

    public override String ToString() {
      return value;
    }
  }

  // ----------------------------------------------------------------
  // An ObjectValue holds a .NET object; may be null

  public class ObjectValue : Value
  {
    public readonly Object value;
    public static readonly ObjectValue nullObjectValue = new ObjectValue(null);
    public static readonly MethodInfo makeMethod
      = typeof(ObjectValue).GetMethod("Make", new Type[] { typeof(System.Object) });

    public ObjectValue(Object value)
    {
      this.value = value;
    }

    public static Value Make(Object o)
    {
      if (o == null)
        return nullObjectValue;
      else 
        return new ObjectValue(o);
    }

    public override Object ToObject()
    {
      return value;
    }

    public override String ToString()
    {
      return value == null ? "null" : value.ToString();
    }
  }

  // ----------------------------------------------------------------
  // An ErrorValue is a value indicating failure of evaluation

  public class ErrorValue : Value {
    public readonly String message;
    public readonly int errorIndex;
    
    // Standard ErrorValue objects and static fields, shared between all functions:
    public new static readonly Type type = typeof(ErrorValue);

    public static readonly MethodInfo
      fromNanMethod = type.GetMethod("FromNan"),
      fromIndexMethod = type.GetMethod("FromIndex");

    // The messageTable maps message to index; errorTable maps index to ErrorValue
    // Invariant: errorTable[messageTable[message]].msg == "#ERR: " + msg
    public static readonly IDictionary<String, int> messageTable
      = new HashDictionary<String, int>();
    public static readonly IList<ErrorValue> errorTable
      = new ArrayList<ErrorValue>();

    public static readonly int
      // The NumError is first so it gets index zero; that's useful 
      // because Math functions produce NaN with error code zero.
      numErrorValueIndex = GetIndex("NumError"),   
      argCountErrorValueIndex = GetIndex("ArgCount"),
      argTypeErrorValueIndex = GetIndex("ArgType"),
      noSuchFunctionErrorValueIndex = GetIndex("NoSuchFunction"),
      refErrorValueIndex = GetIndex("Ref"),
      nameErrorValueIndex = GetIndex("Name");

    public static readonly ErrorValue
      numErrorValue = errorTable[numErrorValueIndex],
      argCountErrorValue = errorTable[argCountErrorValueIndex],
      argTypeErrorValue = errorTable[argTypeErrorValueIndex],
      noSuchFunctionErrorValue = errorTable[noSuchFunctionErrorValueIndex],
      refErrorValue = errorTable[refErrorValueIndex],
      nameErrorValue = errorTable[nameErrorValueIndex];

    public static readonly double
      argCountErrorValueNan = argCountErrorValue.ErrorNan,
      argTypeErrorValueNan = argTypeErrorValue.ErrorNan;

    private ErrorValue(String message, int errorIndex) {
      this.message = "#ERR: " + message;
      this.errorIndex = errorIndex;
    }

    public static int GetIndex(String message)
    {
      int errorIndex;
      if (!messageTable.Find(message, out errorIndex))
      {
        messageTable[message] = errorIndex = errorTable.Count;
        errorTable.Add(new ErrorValue(message, errorIndex));
      }
      return errorIndex;
    }

    public static ErrorValue Make(String message)
    {
      return errorTable[GetIndex(message)];
    }

    public double ErrorNan { 
      get { return MakeNan(errorIndex); } 
    }

    // These two are also called from compiled code, through reflection:

    public static ErrorValue FromNan(double d)
    {
      return errorTable[ErrorCode(d)];
    }

    public static ErrorValue FromIndex(int errorIndex)
    {
      return errorTable[errorIndex];
    }

    public override Object ToObject()
    {
      return (Object)this;
    }

    public override String ToString() {
      return message;
    }

    // From error code index (int) to NaN (double) and back

    public static double MakeNan(int errorIndex)
    {
      long nanbits = System.BitConverter.DoubleToInt64Bits(Double.NaN);
      return System.BitConverter.Int64BitsToDouble(nanbits | errorIndex);
    }

    public static int ErrorCode(double d)
    {
      return (int)System.BitConverter.DoubleToInt64Bits(d);
    }
  }

  // ----------------------------------------------------------------
  // An ArrayValue is a rectangle of Values 

  public abstract class ArrayValue : Value {
    public new static readonly Type type = typeof(ArrayValue);
    public static readonly MethodInfo
      columnsMethod = type.GetMethod("GetColumns"),
      indexMethod = type.GetMethod("Index"),
      rowsMethod = type.GetMethod("GetRows"),
      sliceMethod = type.GetMethod("Slice");

    public abstract int Cols { get; }

    public abstract int Rows { get; }
    
    // Get cell value from array value
    public virtual Value this[CellAddr ca]
    {
      get { return this[ca.col, ca.row]; }
    }

    public abstract Value this[int col, int row] { get; }

    public override Object ToObject()
    {
      return (Object)this;
    }

    // Used to implement INDEX(area,row,col), truncated 1-based indexing
    public Value Index(double deltaRow, double deltaCol)
    {
      if (double.IsNaN(deltaCol))
        return NumberValue.Make(deltaCol);
      else if (double.IsNaN(deltaRow))
        return NumberValue.Make(deltaRow);
      else
      {
        int col = (int)deltaCol - 1, row = (int)deltaRow - 1;
        if (0 <= col && col < Cols && 0 <= row && row < Rows)
          return this[col, row] ?? NumberValue.ZERO;
        else
          return ErrorValue.refErrorValue;
      }
    }

    // Used to implement COLS and ROWS in generated code
    public double GetColumns()
    {
      return (double)Cols;
    }

    public double GetRows()
    {
      return (double)Rows;
    }

    public abstract Value View(CellAddr ulCa, CellAddr lrCa);

    public Value Slice(double r1, double c1, double r2, double c2)
    {
      int ir1 = (int)r1 - 1, ic1 = (int)c1 - 1, ir2 = (int)r2 - 1, ic2 = (int)c2 - 1;
      if (0 <= ir1 && ir1 <= ir2 + 1 && ir2 < Rows && 0 <= ic1 && ic1 <= ic2 + 1 && ic2 < Cols)
        return View(new CellAddr(ic1, ir1), new CellAddr(ic2, ir2));
      else
        return ErrorValue.refErrorValue;
    }

    public void Apply(Act<double> act)
    {
      for (int c = 0; c < Cols; c++)
      {
        for (int r = 0; r < Rows; r++)
        {
          Value v = this[c, r];
          if (v != null) // Only non-blank cells contribute
            if (v is ArrayValue)
              (v as ArrayValue).Apply(act);
            else
              act(Value.ToDoubleOrNan(v));
        }
      }
    }

    public override void Apply(Act<Value> act)
    {
      for (int c = 0; c < Cols; c++)
      {
        for (int r = 0; r < Rows; r++)
        {
          Value v = this[c, r];
          if (v != null) // Only non-blank cells contribute
            if (v is ArrayValue)
              (v as ArrayValue).Apply(act);
            else
             act(v);
        }
      }
    }

    public override String ToString()
    {
      StringBuilder sb = new StringBuilder();
      for (int r = 0; r < Rows; r++)
      {
        for (int c = 0; c < Cols; c++)
        {
          Value v = this[c, r];
          sb.Append(v == null ? "[none]" : v.ToString());
          if (c < Cols-1)
            sb.Append("\t");
        }
        if (r < Rows-1)
          sb.Append("\n");
      }
      return sb.ToString();
    }
  }

  // ----------------------------------------------------------------
  // An ArrayView is a rectangular view of a sheet; the
  // result of evaluating a CellArea expression.  Accessing an 
  // element of an ArrayView may cause a cell to be evaluated.

  public class ArrayView : ArrayValue, IEquatable<ArrayView>
  {
    public readonly CellAddr ulCa, lrCa;  // ulCa to the left and above lrCa
    public readonly Sheet sheet;          // non-null
    private readonly int cols, rows;

    public ArrayView(CellAddr ulCa, CellAddr lrCa, Sheet sheet)
    {
      CellAddr.NormalizeArea(ulCa, lrCa, out this.ulCa, out this.lrCa);
      this.sheet = sheet;
      this.cols = lrCa.col - ulCa.col + 1;  
      this.rows = lrCa.row - ulCa.row + 1;
    }

    public override int Cols { get { return cols; } }

    public override int Rows { get { return rows; } }

    // Evaluate and get value at offset [col, row], 0-based
    public override Value this[int col, int row]
    {
      get
      {
        //    0 <= col && col < Cols
        // && 0 <= row && row < Rows
        int c = ulCa.col + col, r = ulCa.row + row;
        Cell cell = sheet[c, r];
        if (cell != null)
          return cell.Eval(sheet, c, r);
        else
          return null;
      }
    }

    public override Value View(CellAddr ulCa, CellAddr lrCa)
    {
      return new ArrayView(ulCa.Offset(this.ulCa), lrCa.Offset(this.ulCa), sheet);
    }

    // sestoft: Dubious that this should be used currently
    public void Apply(Act<FullCellAddr> act)
    {
      int col0 = ulCa.col, row0 = ulCa.row;
      for (int c = 0; c < cols; c++)
        for (int r = 0; r < rows; r++)
          act(new FullCellAddr(col0 + c, row0 + r, sheet));
    }

    public bool Equals(ArrayView other)
    {
      return sheet == other.sheet && ulCa.Equals(other.ulCa) && lrCa.Equals(other.lrCa);
    }

    public override bool Equals(Object o)
    {
      return o is ArrayView && Equals((ArrayView)o);
    }

    public override int GetHashCode()
    {
      return (ulCa.GetHashCode() * 29 + lrCa.GetHashCode()) * 37 + sheet.GetHashCode();
    }
  }

  // ----------------------------------------------------------------
  // An ArrayExplicit is a (view of) a concrete array of Values.

  public class ArrayExplicit : ArrayValue
  {
    public readonly CellAddr ulCa, lrCa;  // ulCa to the left and above lrCa
    public readonly Value[,] values;          // non-null
    private readonly int cols, rows;

    public ArrayExplicit(Value[,] values) 
      : this(new CellAddr(0, 0), new CellAddr(values.GetLength(0) - 1, values.GetLength(1) - 1), values)
    { }

    public ArrayExplicit(CellAddr ulCa, CellAddr lrCa, Value[,] values)
    {
      this.ulCa = ulCa;
      this.lrCa = lrCa;
      this.values = values;
      this.cols = lrCa.col - ulCa.col + 1;
      this.rows = lrCa.row - ulCa.row + 1;
    }

    public override Value View(CellAddr ulCa, CellAddr lrCa)
    {
      return new ArrayExplicit(ulCa.Offset(this.ulCa), lrCa.Offset(this.ulCa), values);
    }

    public override int Cols { get { return cols; } }

    public override int Rows { get { return rows; } }

    // Evaluate and get value at offset [col, row], 0-based
    public override Value this[int col, int row]
    {
      get
      {
        //    0 <= col && col < Cols
        // && 0 <= row && row < Rows
        int c = ulCa.col + col, r = ulCa.row + row;
        return values[c, r];
      }
    }
  }
  
  // ----------------------------------------------------------------
  // A FunctionValue is a partially applied sheet-defined function

  public class FunctionValue : Value
  {
    public readonly SdfInfo sdfInfo;
    public readonly Value[] args; // args.Length == givenCount
    public readonly int givenCount;

    // Used by code generation for CGApply and CGGetfun
    public new static readonly Type type = typeof(FunctionValue);

    public static readonly MethodInfo
      getFunMethod = type.GetMethod("GetFun");

    public static readonly MethodInfo[] callMethods
      = { type.GetMethod("Call0"), 
          type.GetMethod("Call1"), 
          type.GetMethod("Call2"), 
          type.GetMethod("Call3"), 
          type.GetMethod("Call4"),
          type.GetMethod("Call5"),
          type.GetMethod("Call6"),
          type.GetMethod("Call7"),
          type.GetMethod("Call8"),
          type.GetMethod("Call9") };

    public FunctionValue(SdfInfo sdfInfo, Value[] arguments)
    {
      // There will be no subsequent writes to the arguments array 
      this.sdfInfo = sdfInfo;
      this.args = arguments;
      this.givenCount = arguments.Length;
    }

    public override Object ToObject()
    {
      return (Object)this;
    }

    public Value Apply(Value[] vs)
    {
      if (Arity != vs.Length)
        return ErrorValue.argCountErrorValue;
      else
        switch (Arity)
        {
          case 0: return Call0();
          case 1: return Call1(vs[0]);
          case 2: return Call2(vs[0], vs[1]);
          case 3: return Call3(vs[0], vs[1], vs[2]);
          case 4: return Call4(vs[0], vs[1], vs[2], vs[3]);
          case 5: return Call5(vs[0], vs[1], vs[2], vs[3], vs[4]);
          case 6: return Call6(vs[0], vs[1], vs[2], vs[3], vs[4], vs[5]);
          case 7: return Call7(vs[0], vs[1], vs[2], vs[3], vs[4], vs[5], vs[6]);
          case 8: return Call8(vs[0], vs[1], vs[2], vs[3], vs[4], vs[5], vs[6], vs[7]);
          case 9: return Call9(vs[0], vs[1], vs[2], vs[3], vs[4], vs[5], vs[6], vs[7], vs[8]);
          default: return ErrorValue.Make("Too many arguments");
        }
    }

    // The following are called from compiled SDF to implement GETFUN and APPLY

    public Value Call0()
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call0();
        case 1: return sdfInfo.Call1(args[0]);
        case 2: return sdfInfo.Call2(args[0], args[1]);
        case 3: return sdfInfo.Call3(args[0], args[1], args[2]);
        case 4: return sdfInfo.Call4(args[0], args[1], args[2], args[3]);
        case 5: return sdfInfo.Call5(args[0], args[1], args[2], args[3], args[4]);
        case 6: return sdfInfo.Call6(args[0], args[1], args[2], args[3], args[4], args[5]);
        case 7: return sdfInfo.Call7(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
        case 8: return sdfInfo.Call8(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
        case 9: return sdfInfo.Call9(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call1(Value b1)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call1(b1);
        case 1: return sdfInfo.Call2(args[0], b1);
        case 2: return sdfInfo.Call3(args[0], args[1], b1);
        case 3: return sdfInfo.Call4(args[0], args[1], args[2], b1);
        case 4: return sdfInfo.Call5(args[0], args[1], args[2], args[3], b1);
        case 5: return sdfInfo.Call6(args[0], args[1], args[2], args[3], args[4], b1);
        case 6: return sdfInfo.Call7(args[0], args[1], args[2], args[3], args[4], args[5], b1);
        case 7: return sdfInfo.Call8(args[0], args[1], args[2], args[3], args[4], args[5], args[6], b1);
        case 8: return sdfInfo.Call9(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], b1);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call2(Value b1, Value b2)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call2(b1, b2);
        case 1: return sdfInfo.Call3(args[0], b1, b2);
        case 2: return sdfInfo.Call4(args[0], args[1], b1, b2);
        case 3: return sdfInfo.Call5(args[0], args[1], args[2], b1, b2);
        case 4: return sdfInfo.Call6(args[0], args[1], args[2], args[3], b1, b2);
        case 5: return sdfInfo.Call7(args[0], args[1], args[2], args[3], args[4], b1, b2);
        case 6: return sdfInfo.Call8(args[0], args[1], args[2], args[3], args[4], args[5], b1, b2);
        case 7: return sdfInfo.Call9(args[0], args[1], args[2], args[3], args[4], args[5], args[6], b1, b2);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call3(Value b1, Value b2, Value b3)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call3(                                                      b1, b2, b3);
        case 1: return sdfInfo.Call4(args[0],                                              b1, b2, b3);
        case 2: return sdfInfo.Call5(args[0], args[1],                                     b1, b2, b3);
        case 3: return sdfInfo.Call6(args[0], args[1], args[2],                            b1, b2, b3);
        case 4: return sdfInfo.Call7(args[0], args[1], args[2], args[3],                   b1, b2, b3);
        case 5: return sdfInfo.Call8(args[0], args[1], args[2], args[3], args[4],          b1, b2, b3);
        case 6: return sdfInfo.Call9(args[0], args[1], args[2], args[3], args[4], args[5], b1, b2, b3);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call4(Value b1, Value b2, Value b3, Value b4)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call4(b1, b2, b3, b4);
        case 1: return sdfInfo.Call5(args[0], b1, b2, b3, b4);
        case 2: return sdfInfo.Call6(args[0], args[1], b1, b2, b3, b4);
        case 3: return sdfInfo.Call7(args[0], args[1], args[2], b1, b2, b3, b4);
        case 4: return sdfInfo.Call8(args[0], args[1], args[2], args[3], b1, b2, b3, b4);
        case 5: return sdfInfo.Call9(args[0], args[1], args[2], args[3], args[4], b1, b2, b3, b4);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call5(Value b1, Value b2, Value b3, Value b4, Value b5)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call5(                                    b1, b2, b3, b4, b5);
        case 1: return sdfInfo.Call6(args[0],                            b1, b2, b3, b4, b5);
        case 2: return sdfInfo.Call7(args[0], args[1],                   b1, b2, b3, b4, b5);
        case 3: return sdfInfo.Call8(args[0], args[1], args[2],          b1, b2, b3, b4, b5);
        case 4: return sdfInfo.Call9(args[0], args[1], args[2], args[3], b1, b2, b3, b4, b5);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call6(Value b1, Value b2, Value b3, Value b4, Value b5, Value b6)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call6(                           b1, b2, b3, b4, b5, b6);
        case 1: return sdfInfo.Call7(args[0],                   b1, b2, b3, b4, b5, b6);
        case 2: return sdfInfo.Call8(args[0], args[1],          b1, b2, b3, b4, b5, b6);
        case 3: return sdfInfo.Call9(args[0], args[1], args[2], b1, b2, b3, b4, b5, b6);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call7(Value b1, Value b2, Value b3, Value b4, Value b5, Value b6, Value b7)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call7(                  b1, b2, b3, b4, b5, b6, b7);
        case 1: return sdfInfo.Call8(args[0],          b1, b2, b3, b4, b5, b6, b7);
        case 2: return sdfInfo.Call9(args[0], args[1], b1, b2, b3, b4, b5, b6, b7);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call8(Value b1, Value b2, Value b3, Value b4, Value b5, Value b6, Value b7, Value b8)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call8(         b1, b2, b3, b4, b5, b6, b7, b8);
        case 1: return sdfInfo.Call9(args[0], b1, b2, b3, b4, b5, b6, b7, b8);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public Value Call9(Value b1, Value b2, Value b3, Value b4, Value b5, Value b6, Value b7, Value b8, Value b9)
    {
      switch (givenCount)
      {
        case 0: return sdfInfo.Call9(b1, b2, b3, b4, b5, b6, b7, b8, b9);
        default: return ErrorValue.Make("Too many arguments");
      }
    }

    public static FunctionValue GetFun(int sdfIndex, Value[] arguments)
    {
      return new FunctionValue(SdfManager.GetInfo(sdfIndex), arguments);
    }

    public int Arity { get { return sdfInfo.Arity - givenCount; } }

    public override String ToString()
    {
      StringBuilder sb = new StringBuilder();
      sb.Append(sdfInfo.name).Append("(");
      if (givenCount > 0)
        sb.Append(args[0]);
      for (int i = 1; i < givenCount; i++)
        sb.Append(",").Append(args[i]);
      sb.Append(")");
      return sb.ToString();
    }
  }
}